/*
 * User: user
 * Date: 30/12/2007
 * Time: 00:57:36
 */
package org.conann.metadata;

import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import static com.google.common.collect.Multimaps.newSetMultimap;
import com.google.common.collect.SetMultimap;
import static com.google.common.collect.Sets.newHashSetWithExpectedSize;
import net.jcip.annotations.Immutable;
import org.conann.Stereotype;
import org.conann.util.AnnotationUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.*;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;

@Immutable
/**
 Metadata which is relevant to component definition or resolution such as binding types, scope types, deployment types and stereotypes.
 */
public class BeanMD implements Iterable<Annotation> {

    private final AnnotatedElement annotatedElement;
    private final Set<Annotation> annotations;
    private final Map<Class<?>, Annotation> annotationByClass = Maps.newHashMap();
    private final Map<Annotation, Map<String, Object>> annotationsParams = Maps.newHashMapWithExpectedSize(2);

    private final Set<Annotation> bindingTypes;
    private final Set<Stereotype> stereotypes;
    private final Class<? extends Annotation> scopeType;
    private final Class<? extends Annotation> deploymentType;

    public BeanMD(AnnotatedElement annotatedElement) {
        checkNotNull(annotatedElement);
        this.annotatedElement = annotatedElement;
        annotations = unmodifiableSet(AnnotationUtil.getRelevantAnnotations(annotatedElement.getAnnotations()));
        for (Annotation annotation : annotations) {
            add(annotation);
        }
        SetMultimap<AnnotationType, Annotation> annotationsByType = createAnnotationsMap(annotations);
        bindingTypes = unmodifiableSet(annotationsByType.get(AnnotationType.binding));
        scopeType = getSingleAnnotationClass(annotationsByType, AnnotationType.scope);
        deploymentType = getSingleAnnotationClass(annotationsByType, AnnotationType.deployment);
        stereotypes = createStereotypes(annotationsByType.get(AnnotationType.stereotype));
    }

    private Class<? extends Annotation> getSingleAnnotationClass(SetMultimap<AnnotationType, Annotation> annotationsByType, AnnotationType annotationType) {
        Set<Annotation> annotationsOfType = annotationsByType.get(annotationType);
        if (annotationsOfType.size() == 1) {
            return Iterables.getOnlyElement(annotationsOfType).annotationType();
        }
        return null;
    }

    private Set<Stereotype> createStereotypes(Set<Annotation> annotations) {
        Set<Stereotype> tempStereotypes = newHashSetWithExpectedSize(2);
        for (Annotation annotation : annotations) {
            Stereotype stereotype = StereotypeRegistry.getInstance().get(annotation.annotationType());
            tempStereotypes.add(stereotype);
        }
        return Collections.unmodifiableSet(tempStereotypes);
    }

    private void add(Annotation annotation) {
        checkNotNull(annotation);
        AnnotationType annotationType = AnnotationType.getAnnotationType(annotation);
        checkNotNull(annotationType, "%s is not a component metadata annotation and shouldn't have been added to %s", annotation.annotationType(), BeanMD.class.getSimpleName());
        annotationsParams.put(annotation, AnnotationUtil.getParameters(annotation));
    }

    public boolean contains(Class<? extends Annotation> annotationClass) {
        checkNotNull(annotationClass);
        return annotationByClass.containsKey(annotationClass);
    }

    public Class<? extends Annotation> getDeploymentType() {
        return deploymentType;
    }

    public Class<? extends Annotation> getScopeType() {
        return scopeType;
    }

    public Set<Stereotype> getStereotypes() {
        return stereotypes;
    }

    public Set<Annotation> getBindingTypes() {
        return bindingTypes;
    }

    public Set<Annotation> getAll() {
        return annotations;
    }

    private SetMultimap<AnnotationType, Annotation> createAnnotationsMap(Set<Annotation> annotations) {
        SetMultimap<AnnotationType, Annotation> annotationSetMultimap = newSetMultimap(new EnumMap<AnnotationType, Collection<Annotation>>(AnnotationType.class), new Supplier<Set<Annotation>>() {
            public Set<Annotation> get() {
                return newHashSetWithExpectedSize(4);
            }
        });
        for (Annotation annotation : annotations) {
            annotationSetMultimap.put(AnnotationType.getAnnotationType(annotation), annotation);
        }
        return annotationSetMultimap;
    }

    public Iterator<Annotation> iterator() {
        return annotations.iterator();
    }

    public Annotation getByClass(Class<?> type) {
        checkNotNull(type);
        return annotationByClass.get(type);
    }

    public Map<String, Object> getParams(Annotation annotation) {
        return unmodifiableMap(annotationsParams.get(annotation));
    }

    public AnnotatedElement getAnnotatedElement() {
        return annotatedElement;
    }
}